/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*                                                                  *
*        *************************************************         *
*        *©2021-2031 HT  Corporation All rights reserved *         *
*        *************************************************         *
*                                                                  *
* FileName    : demo_ffmpeg.c                                      *
*                                                                  *
* Author      : linus                                              *
*                                                                  *
* Email       : luoyaojun@haitutech.com                            *
*                                                                  *
* Date        : 2023-3-22                                          *
*                                                                  *
* Description :                                                    *
*                                                                  *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <libavcodec/avcodec.h>
//#include <libavformat/avformat.h>
//#include <libswscale/swscale.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>

#include "libavutil/avutil.h"
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavcodec/avcodec.h"
#include "libavutil/mathematics.h"

#include "demo_ffmpeg.h"
#ifndef VDEC_INT64_MIN
#define VDEC_INT64_MIN (-0x7fffffffffffffffLL - 1)
#endif

#ifndef VDEC_INT64_MAX
#define VDEC_INT64_MAX INT64_C(9223372036854775807)
#endif

static void *ffmpeg_play_mp4_do_work(void *v)
{
	
}


static int alloc_and_copy(AVPacket *out, const uint8_t *sps_pps, uint32_t sps_pps_size, const uint8_t *in, uint32_t in_size)
{
	uint32_t offset = out->size; // 偏移量，就是out已有数据的大小，后面再写入数据就要从偏移量处开始操作
	// 特征码的大小，SPS/PPS占4字节，其余占3字节
	uint8_t nal_header_size = sps_pps == NULL ? 3 : 4;
	int err;
 
	// 每次处理前都要对out进行扩容，扩容的大小就是此次要写入的内容的大小，也就是特征码大小加上sps/pps大小加上加上本帧数据大小
	if ((err = av_grow_packet(out, sps_pps_size + in_size + nal_header_size)) < 0)
		return err;
 
	// 1.如果有sps_pps则先将sps_pps拷贝进out（memcpy()函数用于内存拷贝，第一个参数为拷贝要存储的地方，第二个参数是要拷贝的内容，第三个参数是拷贝内容的大小）
	if (sps_pps)
	{
		memcpy(out->data + offset, sps_pps, sps_pps_size);
	}
 
	// 2.再设置特征码（sps/pps特征码4位0x00000001，其他的特征码3位0x000001）
	for (int i = 0; i < nal_header_size; i++)
	{
		(out->data + offset + sps_pps_size)[i] = i == nal_header_size - 1 ? 1 : 0;
	}
 
	// 3.最后再拷贝NALU数据(当前处理的帧数据)
	memcpy(out->data + sps_pps_size + nal_header_size + offset, in, in_size);
 
	return 0;
}
int h264_extradata_to_annexb(const uint8_t *codec_extradata, const int codec_extradata_size, AVPacket *out_extradata, int padding)
{
	uint16_t unit_size; // sps/pps数据长度
	uint64_t total_size = 0; // 所有sps/pps数据长度加上其特征码长度后的总长度
	printf("aaaa12 h264\n");
 
	//for (int i = 0; i < codec_extradata_size; ++i)
	//{
	//	printf("%02x ", *(codec_extradata + i));
	//}
 
	/*
		out:是一个指向一段内存的指针，这段内存用于存放所有拷贝的sps/pps数据和其特征码数据
		unit_nb:sps/pps个数
		sps_done：sps数据是否已经处理完毕
		sps_seen：是否有sps数据
		pps_seen：是否有pps数据
		sps_offset：sps数据的偏移，为0
		pps_offset：pps数据的偏移，因为pps数据在sps后面，所以其偏移就是所有sps数据长度+sps的特征码所占字节数
	*/
	uint8_t *out = NULL, unit_nb, sps_done = 0,
		sps_seen = 0, pps_seen = 0, sps_offset = 0, pps_offset = 0;
	const uint8_t *extradata = codec_extradata + 4; // 扩展数据的前4位是无用的数据，直接跳过拿到真正的扩展数据
	static const uint8_t nalu_header[4] = { 0, 0, 0, 1 }; // sps/pps数据前面的4bit的特征码
 
	// extradata第一个字节的最后2位用于指示后面每个sps/pps数据所占字节数。(*extradata表示extradata第一个字节的数据，之后自增1指向下一个字节)
	int length_size = (*extradata++ & 0x3) + 1;
 
	sps_offset = pps_offset = -1;
 
	// extradata第二个字节最后5位用于指示sps的个数,一般情况下一个扩展只有一个sps和pps，之后指针指向下一位
	unit_nb = *extradata++ & 0x1f;
	if (!unit_nb) { // unit_nb为0表示没有sps数据，直接跳转到处理pps的地方
		goto pps;
	}
	else { // unit_nb不为0表有sps数据，所以sps_seen赋值1，sps_offset赋值0
		sps_offset = 0;
		sps_seen = 1;
	}
 
	while (unit_nb--) { // 遍历每个sps或pps(先变量sps，然后再遍历pps)
		int err;
 
		// 再接着2个字节表示sps/pps数据的长度
		unit_size = (extradata[0] << 8) | extradata[1];
		total_size += unit_size + 4; // 4表示sps/pps特征码长度
		if (total_size > INT_MAX - padding) { // total_size太大会造成数据溢出，所以要做判断
			av_log(NULL, AV_LOG_ERROR,
				"Too big extradata size, corrupted stream or invalid MP4/AVCC bitstream\n");
			av_free(out);
			return AVERROR(EINVAL);
		}
 
		// extradata + 2 + unit_size比整个扩展数据都长了表明数据是异常的
		if (extradata + 2 + unit_size > codec_extradata + codec_extradata_size) {
			av_log(NULL, AV_LOG_ERROR, "Packet header is not contained in global extradata, "
				"corrupted stream or invalid MP4/AVCC bitstream\n");
			av_free(out);
			return AVERROR(EINVAL);
		}
 
		// av_reallocp()函数用于内存扩展，给out扩展总长加padding的长度
		if ((err = av_reallocp(&out, total_size + padding)) < 0)
			return err;
 
		// 先将4字节的特征码拷贝进out
		memcpy(out + total_size - unit_size - 4, nalu_header, 4);
		// 再将sps/pps数据拷贝进out,extradata + 2是因为那2字节是表示sps/pps长度的，所以要跳过
		memcpy(out + total_size - unit_size, extradata + 2, unit_size);
		// 本次sps/pps数据处理完后，指针extradata跳过本次sps/pps数据
		extradata += 2 + unit_size;
	pps:
		if (!unit_nb && !sps_done++) { // 执行到这里表明sps已经处理完了，接下来处理pps数据
			// pps的个数
			unit_nb = *extradata++;
			if (unit_nb) { // 如果pps个数大于0这给pps_seen赋值1表明数据中有pps
				pps_offset = total_size;
				pps_seen = 1;
			}
		}
	}
 
	if (out) // 如果out有数据，那么将out + total_size后面padding(即64)个字节用0替代
		memset(out + total_size, 0, padding);
 
	// 如果数据中没有sps或pps则给出提示
	if (!sps_seen)
		av_log(NULL, AV_LOG_WARNING,
			"Warning: SPS NALU missing or invalid. "
			"The resulting stream may not play.\n");
 
	if (!pps_seen)
		av_log(NULL, AV_LOG_WARNING,
			"Warning: PPS NALU missing or invalid. "
			"The resulting stream may not play.\n");
 
	// 给传进来的sps/pps的AVPaket赋值
	out_extradata->data = out;
	out_extradata->size = total_size;
 
	return length_size;
}
int h264_mp4toannexb(AVFormatContext *fmt_ctx, AVPacket *in, FILE *file, demo_ffmpeg_frame_out user_cb, unsigned int pts)
{
	AVPacket *out = NULL; // 输出的包
	AVPacket spspps_pkt; // sps/pps数据的AVPaket
 
	uint8_t unit_type; // NALU头中nal_unit_type，也就是NALU类型，5表示是I帧，7表示SPS，8表示PPS
	int32_t nal_size; // 一个NALU(也就是一帧，其第一个字节是头信息)的大小，它存放在NALU的前面的4个字节中
	uint8_t nal_size_len = 4; // 存放nal_size的字节数
	uint32_t cumul_size = 0; // 已经处理的字节数，当cumul_size==buf_size时表示整个包的数据都处理完了
	const uint8_t *buf; // 传进来的数据指针
	const uint8_t *buf_end; // 传进来的数据末尾指针
	int buf_size; // 传进来的数据大小
	int ret = 0, i;
 
	out = av_packet_alloc();
 
	buf = in->data;
	buf_size = in->size;
	buf_end = in->data + in->size; // 数据首地址加上数据大小就是数据尾地址
 
	do {
		ret = AVERROR(EINVAL);
		if (buf + nal_size_len > buf_end) // 说明传进来的数据没有内容，是有问题的
		{
			goto fail;
		}
 
		// 取出NALU前面的4个字节得到这一帧的数据大小
		for (nal_size = 0, i = 0; i < nal_size_len; i++)
		{
			nal_size = (nal_size << 8) | buf[i];
		}
 
		buf += nal_size_len; // buf后移4位指向NALU的头信息(1个字节)
		unit_type = *buf & 0x1f; // 取出NALU头信息的后面5个bit，这5bit记录NALU的类型
 
		// 数据有问题就退出
		if (nal_size > buf_end - buf || nal_size < 0)
		{
			goto fail;
		}
 
		// unit_type是5表示是关键帧，对于关键帧要在其前面添加SPS和PPS信息
		if (unit_type == 5) {
 
			printf("aaaa11 type=%d\n", unit_type);
			// 添加SPS和PPS信息，找FFmpeg中SPS和PPS信息存放在codecpar->extradata中
			h264_extradata_to_annexb(fmt_ctx->streams[in->stream_index]->codecpar->extradata,
				fmt_ctx->streams[in->stream_index]->codecpar->extradata_size,
				&spspps_pkt,
				AV_INPUT_BUFFER_PADDING_SIZE);
 
			// 为数据添加特征码(起始码，用于分隔一帧一帧的数据)
			if ((ret = alloc_and_copy(out,
				spspps_pkt.data, spspps_pkt.size,
				buf, nal_size)) < 0)
				goto fail;
		}
		else {
			// 非关键帧只需要添加特征码
			if ((ret = alloc_and_copy(out, NULL, 0, buf, nal_size)) < 0)
				goto fail;
		}
 
 
		buf += nal_size; // 一帧处理完后将指针移到下一帧
		cumul_size += nal_size + nal_size_len;// 累计已经处理好的数据长度
	} while (cumul_size < buf_size);

		printf("--------------> %p %d\n", out->data, out->size);
		if (user_cb)
			user_cb(0, 0, out->data, out->size, pts, NULL);
	// SPS、PPS和特征码都添加后将其写入文件
//	len = fwrite(out->data, 1, out->size, file);
//	if (len != out->size) {
//		av_log(NULL, AV_LOG_DEBUG, "warning, length of writed data isn't equal pkt.size(%d, %d)\n", len, out->size);
//	}
	// fwrite()只是将数据写入缓存，fflush()才将数据正在写入文件
//	fflush(file);
 
fail:
	av_packet_free(&out); // 凡是中途处理失败退出之前都要将促使的out释放，否则会出现内存泄露
 
	return ret;
 
}

int ffmpeg_mp4_2_h264(const char *file_name)
{
	//parameter
	const char *srcMedia = file_name;
	const char *destMedia = "test1.h264";
	char errors[200] = { 0 };
	AVFormatContext *inFormatContext = NULL;
	AVFormatContext *outFormatContext = NULL;
 
	//
	av_log_set_level(AV_LOG_INFO);
	av_register_all();
	//初始化输入上下文
	int ret = avformat_open_input(&inFormatContext, srcMedia, NULL, NULL);
	printf("=========================== ret = %d\n", ret);
	av_dump_format(inFormatContext, -1, srcMedia, 0);
	//指定视频流
	int vInIndex = av_find_best_stream(inFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	AVStream *vInStream = inFormatContext->streams[vInIndex];	
	//获取编解码器
	AVCodecParameters *inCodecPar = vInStream->codecpar;
	AVCodec *inCodec = avcodec_find_encoder(vInStream->codec->codec_id);
	//创建输出上下文
	outFormatContext = avformat_alloc_context();
	//获取输出格式
	AVOutputFormat *outFormat = av_guess_format(NULL, destMedia, NULL);
	outFormatContext->oformat = outFormat;
	//创建stream
	AVStream *vOutStream = avformat_new_stream(outFormatContext, inCodec);
	//复制编解码参数
	avcodec_parameters_copy(vOutStream->codecpar, vInStream->codecpar);
	vOutStream->codecpar->codec_tag = 0;
//	printf("===========================\n");
//	av_dump_format(outFormatContext, 0, destMedia, 1);
	//打开输出文件
	avio_open(&outFormatContext->pb, destMedia, AVIO_FLAG_WRITE);
	//初始化packet
	AVPacket avPacket;
	av_init_packet(&avPacket);
	//写入头
	avformat_write_header(outFormatContext, NULL);
	
	while (av_read_frame(inFormatContext, &avPacket) >= 0)
	{
		printf("===========================1\n");
		if (avPacket.stream_index == vInIndex)
		{
			//更新pts, dts, duration
			avPacket.pts = av_rescale_q_rnd(avPacket.pts, vInStream->time_base, vOutStream->time_base,AV_ROUND_NEAR_INF);
			avPacket.dts = av_rescale_q_rnd(avPacket.dts, vInStream->time_base, vOutStream->time_base, AV_ROUND_NEAR_INF);
			avPacket.duration = av_rescale_q(avPacket.duration, vInStream->time_base, vOutStream->time_base);
			avPacket.pos = -1;
			avPacket.stream_index = 0;
			av_interleaved_write_frame(outFormatContext, &avPacket);
		}
		// 清空数据
		av_packet_unref(&avPacket);
	}
	//释放资源
	av_write_trailer(outFormatContext);
	avformat_close_input(&inFormatContext);
	avio_close(outFormatContext->pb);
 
	return 0;

}
int ffmpeg_play_mp4(const char *file_name, demo_ffmpeg_frame_out user_cb, void *user)
{
    AVFormatContext *pFormatCtx = NULL;
	int videoStreamIndex;
	AVPacket avPkt;
	AVStream *vStream = NULL;
	unsigned int u64PTS = 0;

	av_register_all();
	
    if (avformat_open_input(&pFormatCtx, file_name, NULL, NULL) != 0) {
        printf("open file error\n");
        return -1;
    }
	
	videoStreamIndex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	if(videoStreamIndex < 0)
	{
		return NULL;
	}

	av_init_packet(&avPkt);
	avPkt.data = NULL;
	avPkt.size = 0;

	while((av_read_frame(pFormatCtx, &avPkt) >= 0))
	{
		if(avPkt.stream_index == videoStreamIndex)
		{
			vStream = pFormatCtx->streams[avPkt.stream_index];
			u64PTS = av_q2d(vStream->time_base) * (avPkt.pts) * 1000000ll;
//			printf("---------> %p %d\n", avPkt.data, avPkt.size);
			h264_mp4toannexb(pFormatCtx, &avPkt, NULL, user_cb, u64PTS);
//			if (user_cb)
//				user_cb(avPkt.data, avPkt.size, u64PTS, user);
		}

		av_packet_unref(&avPkt);
	}
	
	avformat_close_input(&pFormatCtx);

    return 0;


}

int ffmpeg_play_h264(const char *file_name, demo_ffmpeg_frame_out user_cb, void *user)
{
	int ret = -1;
	AVFormatContext *pFormatCtx = NULL;
	int i = 0;
	AVStream *vStream = NULL;
	AVPacket avPkt;
	int videoStreamIndex = 0;
	unsigned int u64PTS = 0;
	int width=1920;
    int height=1080;
	
	if (!file_name)
		return -1;
	
	av_register_all();
	
	ret = avformat_open_input(&pFormatCtx, file_name, NULL, NULL);
	if (ret)
	{
		printf("Can not open file [%s]\n", file_name);
		return ret;
	}

	videoStreamIndex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	if(videoStreamIndex < 0)
	{
		return ret;
	}

//	printf("info.avfc = %p ret = %d info.avfc->nb_streams = %d\n", pFormatCtx, ret, pFormatCtx->nb_streams);
	
//	for (i = 0; i < pFormatCtx->nb_streams; i++)
//	{
//		vStream = pFormatCtx->streams[i];
//		printf("------------------------%p %p\n", vStream, vStream->codecpar);

//		if (NULL != vStream && NULL != vStream->codecpar && vStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
//		{
//			videoStreamIndex = i;
//			width = (vStream->codecpar->width > 0) ? vStream->codecpar->width : 1920;
//			height = (vStream->codecpar->height > 0) ? vStream->codecpar->height : 1080;
//			break;
//		}
//	}

	printf("------------------------\n");
	av_init_packet(&avPkt);
	
REDO:
	while (av_read_frame(pFormatCtx, &avPkt) >= 0)
	{
		if (avPkt.stream_index == videoStreamIndex)
		{
			vStream = pFormatCtx->streams[avPkt.stream_index];
			u64PTS = av_q2d(vStream->time_base) * (avPkt.pts) * 1000000ll;

			if (user_cb)
				user_cb(width, height, avPkt.data, avPkt.size, u64PTS, user);
		}

		av_packet_unref(&avPkt);
	}

	avformat_seek_file(pFormatCtx, videoStreamIndex, VDEC_INT64_MIN, 0, VDEC_INT64_MAX, AVSEEK_FLAG_BYTE);
	
goto REDO;

}
